/**
  ******************************************************************************
  * @file    sym32l010_hal_SoftEE.c
  * @author
  * @version 1.0.3
  * @date    2024-05-28
  * @brief   Flash SoftEE HAL module driver.
  *
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2024 SIYIMicro.
  * All rights reserved.</center></h2>
  *
  *
  ******************************************************************************
  */


/******************************************************************************/
/* Include files                                                              */
/******************************************************************************/
#include "sym32l010_hal_SoftEE.h"
#include "sym32l010_hal_flash.h"
#include "sym32l010_hal_crc.h"


/******************************************************************************/
/* Pre-processor symbols/macros ('#define')                                   */
/******************************************************************************/
#define SOFTEE_STARTADDR    (SOFTEE_STARTPAGE_IDX * 512U)
#define SOFTEE_STOPADDR     (SOFTEE_STOPPAGE_IDX * 512U + 511U)
#define SOFTEE_RECORDSIZE   (SOFTEE_SIZE + 2U)


/*******************************************************************************
  * @brief  向EERecord记录中增加CRC值
  * @param  pRecord:待CRC校验的数据指针
  * @retval None
  */
void EeRecord_AddCRC(uint8_t *pRecord)
{
    uint16_t tmp16           = HAL_CRC_Calc_8bit(CRC16_X25, pRecord, SOFTEE_SIZE);
    pRecord[SOFTEE_SIZE]     = (tmp16 >> 0U) & 0xFF;
    pRecord[SOFTEE_SIZE + 1] = (tmp16 >> 8U) & 0xFF;
}


/*******************************************************************************
  * @brief  检查EERECORD尾部的CRC是否正确
  * @param  pRecord:待CRC校验的数据指针
  * @retval HAL_OK     : 代表正确
  *         HAL_ERROR  : 代表错误
  */
HAL_StatusTypeDef EeRecord_CheckCRC(uint8_t *pRecord)
{
    uint16_t tmp16 = HAL_CRC_Calc_8bit(CRC16_X25, pRecord, SOFTEE_RECORDSIZE);

    return ((tmp16 == 0x0F47) ? HAL_OK : HAL_ERROR);
}


/*******************************************************************************
  * @brief  检查EERECORD的内容是否全为FF
  * @param  pRecord：数据指针
  * @retval HAL_OK     : 是
  *         HAL_ERROR  : 否
  */
HAL_StatusTypeDef EeRecord_CheckFF(uint8_t *pRecord)
{
    for (uint8_t i = 0; i < SOFTEE_RECORDSIZE; i++)
    {
        if (*pRecord != 0xFF)
        {
            return HAL_ERROR;
        }
        pRecord++;
    }

    return HAL_OK;
}


/*******************************************************************************
  * @brief  向EE区域的指定地址，写入一条记录
  * @param  RecordAddr：指定地址
  * @param  pRecord   ：待写入的数据指针
  * @retval None
  */
void EeRecord_Write(uint32_t RecordAddr, uint8_t *pRecord)
{
    EeRecord_AddCRC(pRecord);

    HAL_FLASH_UnlockPages(RecordAddr, RecordAddr);
    HAL_FLASH_WriteBytes(RecordAddr, pRecord, SOFTEE_RECORDSIZE);
    HAL_FLASH_LockPages();
}


/*******************************************************************************
  * @brief  在PageAddr所在的页面添加页面标记
  * @param  PageAddr : 页地址
  * @param  PageFlage: 页标记
  *                    0x00FF代表当前工作页面
  *                    0X0000代表本页面数据空间已使用完成
  *                    0xFFFF代表本页面内还没有数据
  * @retval None
  */
void EeRecord_PageFlag_Modify(uint32_t PageAddr, uint16_t PageFlage)
{
    uint32_t PageFlag_Addr = (PageAddr | 0x1ff) - 1;
    uint16_t tmp16         = *((uint16_t *) PageFlag_Addr);
    if (tmp16 != PageFlage)                                           // 检查工作页面记录标记是否存在
    {

        if (PageFlage == 0x00FF)                                      // 0XFFFF -> 0X00FF
        {
            uint8_t tmp8 = 0x00;
            HAL_FLASH_UnlockPages(PageFlag_Addr + 1, PageFlag_Addr + 1);
            HAL_FLASH_WriteBytes(PageFlag_Addr + 1, &tmp8, 1);
            HAL_FLASH_LockPages();
        }

        if (PageFlage == 0x0000)                                      // 0X00FF -> 0X0000
        {
            uint8_t tmp8 = 0x00;

            HAL_FLASH_UnlockPages(PageFlag_Addr, PageFlag_Addr);
            HAL_FLASH_WriteBytes(PageFlag_Addr, &tmp8, 1);
            HAL_FLASH_LockPages();
        }
    }
}


/*******************************************************************************
  * @brief  查找EE区域中的最后一条有效数据
  * @param  pBuf       : 最后一条有效数据的内容
  * @param  pRecordAddr: pRecordAddr最后一条有效数据的存储地址
  * @retval HAL_OK     : 代表是否找到最后一条有效记录
  *         HAL_ERROR  : 代表没有找到最后一条记录
  */
HAL_StatusTypeDef EeRecord_FindLastRecord(uint8_t *pBuf, uint32_t *pRecordAddr)
{
    uint16_t tmp16;
    uint8_t  RecordBuf[SOFTEE_RECORDSIZE];
    HAL_StatusTypeDef RecordFlag = HAL_ERROR;
    uint32_t tmp32, WorkPageAddr_Start, WorkPageAddr_End;

    // step1 找到当前的工作页面
    WorkPageAddr_Start = 0x00;
    WorkPageAddr_End   = SOFTEE_STARTADDR | 0x1ff;
    while (WorkPageAddr_End <= SOFTEE_STOPADDR)
    {
        tmp16 = *((uint16_t *)(WorkPageAddr_End - 1));
        if (tmp16 == 0x00FF)
        {
            WorkPageAddr_Start = WorkPageAddr_End & 0xfffffe00;
            break;
        }
        WorkPageAddr_End += 512;
    }

    // step2 找到最后一条有效记录
    if (WorkPageAddr_Start != 0x00)                                   // 之前已找到工作页面
    {
        tmp32 = WorkPageAddr_Start;
        while ((tmp32 + SOFTEE_RECORDSIZE + 2) <= WorkPageAddr_End)
        {
            HAL_FLASH_ReadBytes(tmp32, RecordBuf, SOFTEE_RECORDSIZE);
            if (HAL_OK == EeRecord_CheckCRC(RecordBuf))
            {
                RecordFlag   = HAL_OK;
                *pRecordAddr = tmp32;
            }
            tmp32 = tmp32 + SOFTEE_RECORDSIZE;
        }

        if (RecordFlag == HAL_OK)
        {
            HAL_FLASH_ReadBytes(*pRecordAddr, pBuf, SOFTEE_SIZE);
        }
    }

    return RecordFlag;
}


/*******************************************************************************
  * @brief  从RefAddr地址开始，找出下一条记录的存储地址
  * @param  RefAddr：起始地址
  * @retval 返回数据为新的存储地址
  */
uint32_t EeRecord_FindNewSpace(uint32_t RefAddr)
{
    uint8_t RecordBuf[SOFTEE_RECORDSIZE];

    uint32_t PageEndAddr = RefAddr | 0x1ff;
    while ((RefAddr + SOFTEE_RECORDSIZE + 2) <= PageEndAddr)
    {
        HAL_FLASH_ReadBytes(RefAddr, RecordBuf, SOFTEE_RECORDSIZE);
        if (HAL_OK == EeRecord_CheckFF(RecordBuf))
        {
            break;
        }
        RefAddr += SOFTEE_RECORDSIZE;
    }

    if ((RefAddr + SOFTEE_RECORDSIZE + 2) > PageEndAddr)              // 本页没有空间了
    {
        RefAddr = PageEndAddr + 1;                                    // 换到下一页
        if (RefAddr >= SOFTEE_STOPADDR)
        {
            RefAddr = SOFTEE_STARTADDR;
        }

        HAL_FLASH_UnlockPages(RefAddr, RefAddr);
        HAL_FLASH_ErasePages(RefAddr, RefAddr);
        HAL_FLASH_LockPages();
    }

    return RefAddr;
}


/*******************************************************************************
  * @brief  追加一条数据记录
  * @param  LastRecordAddr: 为上一条有效记录的地址
  * @param  pRecord       : 待追加的记录的内容
  * @retval 返回数据为新的存储地址
  */
void EeRecord_Append(uint32_t LastRecordAddr, uint8_t *pRecord)
{
    uint32_t NewRecordAddr = EeRecord_FindNewSpace(LastRecordAddr);   // 找到存储位置
    EeRecord_Write(NewRecordAddr, pRecord);                           // 写入数据
    EeRecord_PageFlag_Modify(NewRecordAddr, 0x00FF);                  // 增加工作页面标记
    if ((NewRecordAddr & 0xFFFFFE00) != (LastRecordAddr & 0xFFFFFE00)) // 已换页
    {
        EeRecord_PageFlag_Modify(LastRecordAddr, 0x0000);             // 清除上一个页面的工作标记
    }
}


/*******************************************************************************
  * @brief  EE区数据初始化
  * @param  None
  * @retval None
  * @note   需要使能FLASH及CRC的时钟
  */
void SOFTEE_Init(void)
{
    for (uint32_t Addr=SOFTEE_STARTADDR; Addr<=SOFTEE_STOPADDR; Addr++)
    {
        if (*((uint8_t *)Addr) != 0xFF)
        {
            return;
        }
    }

    // 芯片内还没有将EE区初始化
    uint8_t RecordBuf[SOFTEE_RECORDSIZE];
    for (uint8_t i=0; i<SOFTEE_SIZE; i++)                             // 添加第一条记录
    {
        RecordBuf[i] = 0x00;
    }
    EeRecord_Append(SOFTEE_STARTADDR, RecordBuf);
}


/*******************************************************************************
  * @brief  从EE区的EeAddr处读出RdCnt个字节，存入pBuf
  * @param  EeAddr   : 读数据的起始地址
  * @param  pRdBuf   : 缓存读取数据的指针
  * @param  RdCnt    : 待读出的字节数
  * @retval HAL_OK   : 代表读出成功
  *         HAL_ERROR: 代表读出失败
  * @note   需要使能FLASH及CRC的时钟
  */
HAL_StatusTypeDef SOFTEE_Read(uint8_t EeAddr, uint8_t *pRdBuf, uint8_t RdCnt)
{
    HAL_StatusTypeDef  tmp8 = HAL_ERROR;
    uint8_t  RecordBuf[SOFTEE_RECORDSIZE];
    uint32_t tmp32;

    if ((EeAddr + RdCnt) > SOFTEE_SIZE)                               // 待读取的数据超出记录的总数
    {
        tmp8 = HAL_ERROR;
    }
    else
    {
        tmp8 = EeRecord_FindLastRecord(RecordBuf, &tmp32);
        if (tmp8 == HAL_OK)                                           // 已找到最新的记录
        {
            while (RdCnt)
            {
                *pRdBuf = RecordBuf[EeAddr];
                EeAddr++;
                pRdBuf++;
                RdCnt--;
            }
        }
    }

    return tmp8;
}


/*******************************************************************************
  * @brief  将pWrBuf内的WrCnt个数据写入EE区
  * @param  EeAddr   : 写数据的起始地址
  * @param  pWrBuf   : 待写入的数据指针
  * @param  WrCnt    : 待写入的字节数
  * @retval HAL_OK   : 代表写入成功
  *         HAL_ERROR: 代表写入失败
  * @note   需要使能FLASH及CRC的时钟
  */
HAL_StatusTypeDef SOFTEE_Write(uint8_t EeAddr, uint8_t *pWrBuf, uint8_t WrCnt)
{
    HAL_StatusTypeDef  tmp8 = HAL_ERROR;
    uint8_t  RecordBuf[SOFTEE_RECORDSIZE];
    uint32_t LastRecordAddr;

    if ((EeAddr + WrCnt) > SOFTEE_SIZE)                               // 待写入的数据超出记录的总数
    {
        tmp8 = HAL_ERROR;
    }
    else
    {
        tmp8 = EeRecord_FindLastRecord(RecordBuf, &LastRecordAddr);
        if (tmp8 == HAL_OK)                                           // 已找到最新的记录
        {
            while (WrCnt)
            {
                RecordBuf[EeAddr] = *pWrBuf;
                EeAddr++;
                pWrBuf++;
                WrCnt--;
            }
            EeRecord_Append(LastRecordAddr, RecordBuf);
        }
    }

    return tmp8;
}


/************************ (C) COPYRIGHT SIYIMicro *****END OF FILE*************/
